#include "Interpreter.hpp"
#include <pthread.h>
#include <signal.h>

Job::Job(Shell *owner, int jobid, const string &cmd, int termWidth, int termHeight)
{
	this->owner = owner;
	this->jobid = jobid;
	this->state = STATE_RUNNING;
	
	RPCParams beginJobParams;
		beginJobParams.set("job", jobid);
		this->outputSender = new StreamSender(clientConnection, &beginJobParams);
	clientConnection->call("beginjob", &beginJobParams);
	
	this->pty = ptyManager->getNewPty(outputSender, termWidth, termHeight);
	pthread_mutex_init(&ptyInputLock, NULL);
	
	this->execEnv.pty = pty;
	this->state = new ShellState(*owner->getState());
	
	this->command = cmd;
	this->waitingForDismissal = false;
}

Job::~Job()
{
	pthread_mutex_destroy(&ptyInputLock);
}

void Job::signal(string sig)
{
	jobEvents.addEvent(new JobSignalEvent(sig));
}

int Job::getRunState()
{
	return runState;
}

void Job::setRunState(int runState, bool notifyClient)
{
	this->runState = runState;
	
	if(notifyClient)
	{
		const char *stateString = NULL;
		switch(runState) {
			default: /*FALLTHRU*/
			case STATE_RUNNING: stateString = "running"; break;
			case STATE_FINISHED: stateString = "finished"; break;
			case STATE_BACKGROUNDED: stateString = "background"; break;
		}
		RPCParams setStateParams;
			setStateParams.set("job", jobid);
			setStateParams.set("state", stateString);
		clientConnection->call("jobstate", &setStateParams);
	}
}

void Job::begin()
{
	pthread_create(&thread, NULL, jobThreadFunc, this);
}

void *jobThreadFunc(void *job)
{
	((Job*)job)->threadMain();
	return NULL;
}

void Job::threadMain()
{
	int result = 0;
	
	try
	{
		result = executeText(command.c_str(), &execEnv, this);
	}
	catch(JobTerminated terminateMessage)
	{
		bool retry;
		do {
			retry = false;
			if(terminateMessage.forceKill)
			{
				// Send SIGKILL to all child processes
				for(set<pid_t>::iterator ii=processesRunning.begin(); ii!=processesRunning.end(); ii++)
					kill(*ii, SIGKILL);
				result = 128+SIGKILL;
			}
			else
			{
				// Send SIGTERM to all child processes
				for(set<pid_t>::iterator ii=processesRunning.begin(); ii!=processesRunning.end(); ii++)
					kill(*ii, SIGTERM);
				result = 128+SIGTERM;
			}
			
			// Wait for them to finish. If we get another kill message, handle
			// it (send SIGTERM to everything again, or send SIGKILL to
			// everything)
			try {
				while(processesRunning.size())
					handleEvents();
			} catch(JobTerminated nextTerminate) {
				terminateMessage = nextTerminate;
				retry = true;
			}
		} while(retry);
	}
	
	finish(result);
	pthread_exit(NULL);
}

int Job::waitForProcess(pid_t pid)
{
	while(processesFinished.find(pid) == processesFinished.end())
	{
		handleEvents();
	}
	
	int ret = processesFinished[pid];
	processesFinished.erase(pid);
	return ret;
}

void Job::handleEvents()
{
	jobEvents.waitForEvents();
	
	Event *event;
	while((event = jobEvents.nextEvent())) {
		handleEvent(event);
		delete event;
	}
}

void Job::handleEvent(Event *event)
{
	if(dynamic_cast<ProcessFinishedEvent*>(event))
	{
		int pid = ((ProcessFinishedEvent*)event)->pid;
		int result = ((ProcessFinishedEvent*)event)->result;
		
		if(processesRunning.find(pid) != processesRunning.end())
		{
			processesFinished[pid] = result;
			processesRunning.erase(processesRunning.find(pid));
		}
	}
	else if(dynamic_cast<ProcessStartedEvent*>(event))
	{
		int pid = ((ProcessStartedEvent*)event)->pid;
		processesRunning.insert(pid);
	}
	else if(dynamic_cast<JobSignalEvent*>(event))
	{
		waitingForDismissal = false;
		string sig = ((JobSignalEvent*)event)->sig;
		
		if(sig == "done")
		{
		}
		else if(sig == "int")
		{
			for(set<pid_t>::iterator ii=processesRunning.begin(); ii!=processesRunning.end(); ii++)
			{
				kill(*ii, SIGINT);
			}
		}
		else if(sig == "term")
		{
			throw JobTerminated(false);
		}
		else if(sig == "kill")
		{
			throw JobTerminated(true);
		}
		else if(sig == "pause")
		{
			// TODO
		}
	}
}

void Job::write(const char *message)
{
	outputSender->send(strlen(message), message);
}

void Job::startClientWidget()
{
	ptyManager->readAllPty(pty);
	ptyManager->stopPty(pty);
}

void Job::waitForDismissal()
{
	waitingForDismissal = true;
	while(waitingForDismissal)
		handleEvents();
}

void Job::endClientWidget()
{
	RPCParams outputParams;
		outputParams.set("job", jobid);
		this->outputSender = new StreamSender(clientConnection, &outputParams);
	ptyManager->restartPty(pty, outputSender);
	clientConnection->call("output", &outputParams);
}

void Job::reportError(const char *message)
{
	startClientWidget();
	RPCParams errorParams;
		errorParams.set("job", jobid);
		errorParams.set("message", message);
	clientConnection->call("error", &errorParams);
	endClientWidget();
}

void Job::addProcess(pid_t pid)
{
	jobEvents.addEvent(new ProcessStartedEvent(pid));
}

void Job::processFinished(pid_t pid, int result)
{
	jobEvents.addEvent(new ProcessFinishedEvent(pid, result));
}

void Job::sendEvent(Event *event)
{
	jobEvents.addEvent(event);
}

void Job::finish(int result)
{
	setRunState(STATE_FINISHED, false);
	eventManager->addEvent(new JobFinishedEvent(jobid, pty, result, state));
}

void Job::handleInput(const char *input, int len)
{
	pthread_mutex_lock(&ptyInputLock);
		pty->sendInput(input, len);
	pthread_mutex_unlock(&ptyInputLock);
}

ShellState *Job::getState()
	{ return state; }

void Job::shellExit()
{
	eventManager->addEvent(new ExitShellEvent(0));
}

//////////////////////////////////////////////////////////////////////////////

class JobInputReceiver
	:public StreamReceiver
{
public:
	JobInputReceiver(RemoteHost *source, const RPCParams *params, Job *job);
	void recv(int len, const char *data);
	void onClose();
	
private:
	Job *job;
};

JobInputReceiver::JobInputReceiver(RemoteHost *source, const RPCParams *params, Job *job)
	:StreamReceiver(source, params)
{
	this->job = job;
}

void JobInputReceiver::recv(int len, const char *data)
{
	job->handleInput(data,len);
}

void JobInputReceiver::onClose()
{
}

RPC_HANDLER(input)
{
	int jobid = params->getInt("job");
	Job *job = shell->findJob(jobid);
	if(!job) return;
	JobInputReceiver *receiver = new JobInputReceiver(host, params, job);
}

//////////////////////////////////////////////////////////////////////////////

ProcessFinishedEvent::ProcessFinishedEvent(pid_t pid, int result)
{
	this->pid = pid;
	this->result = result;
}

ProcessStartedEvent::ProcessStartedEvent(pid_t pid)
{
	this->pid = pid;
}

JobSignalEvent::JobSignalEvent(string sig)
{
	this->sig = sig;
}

//////////////////////////////////////////////////////////////////////////////

JobTerminated::JobTerminated(bool forceKill)
{
	this->forceKill = forceKill;
}

